package com.common.file.zip;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.extern.slf4j.Slf4j;
import net.lingala.zip4j.ZipFile;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.model.enums.AesKeyStrength;
import net.lingala.zip4j.model.enums.EncryptionMethod;
import org.apache.commons.compress.archivers.zip.ParallelScatterZipCreator;
import org.apache.commons.compress.archivers.zip.UnixStat;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.compress.parallel.InputStreamSupplier;
import org.apache.commons.io.input.NullInputStream;
import org.apache.commons.lang3.StringUtils;

import java.io.*;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * description: ZipUtil <br>
 * date: 2022/11/23  <br>
 * author: zss <br>
 * version: 1.0.0 <br>
 */
@Slf4j
public class ZipUtil {

    public static byte[] compress(List<BytesFile> srcFileList) throws Exception {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ZipOutputStream zos = new ZipOutputStream(bos);
        zos.setMethod(8);
        zos.setLevel(9);
        if (srcFileList != null) {
            for (BytesFile file : srcFileList) {
                compressFileTmp(file, zos);
            }
        }
        zos.close();
        return bos.toByteArray();
    }

    public static BytesFile compress(String filename,List<BytesFile> srcFileList) throws Exception {
        byte[] bytes = compress(srcFileList);
        BytesFile zipFile = new BytesFile();
        zipFile.setFileBytes(bytes);
        zipFile.setFilename(filename);
        return zipFile;
    }

    public static boolean compress(String filename,List<File> filesToAdd, String password) {
        ZipParameters zipParameters = new ZipParameters();
        zipParameters.setEncryptFiles(true);
        zipParameters.setEncryptionMethod(EncryptionMethod.AES);
// Below line is optional. AES 256 is used by default. You can override it to use AES 128. AES 192 is supported only for extracting.
        zipParameters.setAesKeyStrength(AesKeyStrength.KEY_STRENGTH_256);

        try(ZipFile zipFile = new ZipFile(filename, password.toCharArray())) {
            zipFile.addFiles(filesToAdd, zipParameters);
        } catch (IOException e) {
            log.error("压缩失败",e);
            return false;
        }
        return true;
    }

    public static void extractAll(String destinationPath, String zip, String password){
        ZipFile zipFile = new ZipFile(zip, password.toCharArray());
        try {
            zipFile.extractAll(destinationPath);
        } catch (ZipException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 压缩文件
     */
    private static void compressFileTmp(BytesFile zipFile, ZipOutputStream zos) {
        try {
            zos.putNextEntry(new ZipEntry(zipFile.getFilename()));
            zos.write(zipFile.getFileBytes());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     *  批量压缩文件 v4.0
     *
     * @param fileNameList 需要压缩的文件名称列表(包含相对路径)
     * @param zipOutName 压缩后的文件名称
     **/
    public static void compressFileList(String zipOutName, List<String> fileNameList) throws IOException, ExecutionException, InterruptedException {
        ThreadFactory factory = new ThreadFactoryBuilder().setNameFormat("compressFileList-pool-").build();
        ExecutorService executor = new ThreadPoolExecutor(5, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(20), factory);
        ParallelScatterZipCreator parallelScatterZipCreator = new ParallelScatterZipCreator(executor);
        OutputStream outputStream = new FileOutputStream(zipOutName);
        ZipArchiveOutputStream zipArchiveOutputStream = new ZipArchiveOutputStream(outputStream);
        zipArchiveOutputStream.setEncoding("UTF-8");
        for (String fileName : fileNameList) {
            File inFile = new File(fileName);
            final InputStreamSupplier inputStreamSupplier = () -> {
                try {
                    return new FileInputStream(inFile);
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                    return new NullInputStream(0);
                }
            };
            ZipArchiveEntry zipArchiveEntry = new ZipArchiveEntry(inFile.getName());
            zipArchiveEntry.setMethod(ZipArchiveEntry.DEFLATED);
            zipArchiveEntry.setSize(inFile.length());
            zipArchiveEntry.setUnixMode(UnixStat.FILE_FLAG | 436);
            parallelScatterZipCreator.addArchiveEntry(zipArchiveEntry, inputStreamSupplier);
        }
        parallelScatterZipCreator.writeTo(zipArchiveOutputStream);
        zipArchiveOutputStream.close();
        outputStream.close();
//        log.info("ParallelCompressUtil->ParallelCompressUtil-> info:{}", JSONObject.toJSONString(parallelScatterZipCreator.getStatisticsMessage()));
    }
    public static void compress(String targetDir, String compressFilePath){
        try{
            File sourceFile = new File(targetDir);
            if(StringUtils.isBlank(compressFilePath)){
                compressFilePath = sourceFile.getParentFile().getAbsolutePath() + File.separator + sourceFile.getName() + ".zip";
            }
            ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(compressFilePath));
            compress(sourceFile, sourceFile.getName(), zipOutputStream);
            zipOutputStream.close();
        }catch (Exception e){
            log.error("压缩文件异常!", e);
        }
    }
    private static void compress(File sourceDir, String zipDirName, ZipOutputStream targetZipOut){
        if(!sourceDir.exists()){
            log.debug("待压缩的目录"+sourceDir.getName()+"不存在");
            return;
        }

        File[] files = sourceDir.listFiles();
        if(files == null || files.length ==0){
            return;
        }

        FileInputStream fis = null;
        BufferedInputStream bis = null;
        byte[] byteArray = new byte[1024*10];

        try{
            for (File file : files) {
                if (file.isFile()) {
                    log.debug("开始压缩:{}", file.getAbsoluteFile());
                    ZipEntry zipEntry = new ZipEntry(zipDirName + File.separator + file.getName());
                    targetZipOut.putNextEntry(zipEntry);
                    //读取待压缩的文件并写进压缩包里
                    fis = new FileInputStream(file);
                    bis = new BufferedInputStream(fis, 1024 * 10);
                    int read;
                    while ((read = bis.read(byteArray, 0, 1024 * 10)) != -1) {
                        targetZipOut.write(byteArray, 0, read);
                    }
                    //如果需要删除源文件，则需要执行下面2句
                    //fis.close();
                    //fs[i].delete();
                } else if (file.isDirectory()) {
                    log.debug("进入目录:{}", file.getAbsoluteFile());
                    compress(file, file.getName(), targetZipOut);
                }
            }//end for
        }catch  (IOException e) {
            log.error("打包异常!",e);
        } finally{
            //关闭流
            try {
                if(null!=bis) bis.close();
                if(null!=fis) fis.close();
            } catch (IOException e) {
                log.error("打包关闭流异常!",e);
            }
        }
    }

    public static void main(String[] args) {
        File file = new File("D:\\Test\\excel");
        compress("d:\\Test\\aes.zip", Arrays.asList(file.listFiles()), "123456");

        extractAll("d:\\Test\\aes11","d:\\Test\\aes.zip","123456");
    }
}
